package com.ccloud.gateway.core.config.security;

import cn.hutool.core.collection.CollectionUtil;
import com.ccloud.auth.common.model.TokenConstant;
import com.ccloud.common.core.utils.StringUtils;
import com.ccloud.gateway.core.config.SysParameterConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;

@Component
//@Primary
@Slf4j
public class CustomAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    @Resource
    private SysParameterConfig sysParameterConfig;

    @Resource
    private TokenStore tokenStore;

    /**
     * Determines if access is granted for a specific authentication and object.
     *
     * @param authenticationMono the Authentication to check
     * @param object             the object to check
     * @return an decision or empty Mono if no decision could be made.
     */
    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authenticationMono, AuthorizationContext object) {
        ServerWebExchange exchange = object.getExchange();
        String requestPath = exchange.getRequest().getURI().getPath();
        log.info("当前请求路径：{}", requestPath);

        PathMatcher pathMatcher = new AntPathMatcher();
        //校验当前请求的url是否在白名单内
        List<String> whiteUrlList = sysParameterConfig.getIgnoreUrls();
        if (CollectionUtil.isNotEmpty(whiteUrlList)) {
            for (String whiteUrl : whiteUrlList) {
                if (pathMatcher.match(whiteUrl, requestPath)) {
                    //将请求头的token移除，避免授权服务校验token
                    ServerHttpRequest request = exchange.getRequest().mutate()
                            .headers(httpHeaders -> httpHeaders.remove("Authorization")).build();
                    log.info("url: {}, path: {},白名单地址直接放行,并将当前的请求头的token移除", whiteUrl, requestPath);
                    exchange.mutate().request(request).build();
                    return Mono.just(new AuthorizationDecision(true));
                }
            }
        }

        String authorizationToken = exchange.getRequest().getHeaders().getFirst(TokenConstant.TOKEN_NAME);
        log.info("当前请求头Authorization中的值:{}", authorizationToken);
        if (StringUtils.isBlank(authorizationToken)) {
            log.warn("当前请求头Authorization中的值不存在");
            return Mono.just(new AuthorizationDecision(false));
        }

        String token = authorizationToken.replace(OAuth2AccessToken.BEARER_TYPE + " ", "");
        OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(token);
        log.info("当前token所拥有的OAuth2Authentication:{}", oAuth2Authentication);
        Collection<GrantedAuthority> authorities = oAuth2Authentication.getAuthorities();
        log.info("当前token所拥有的权限:{}", authorities.toString());

        for (GrantedAuthority authority : authorities) {
            String authorityStr = authority.getAuthority();
            if (pathMatcher.match(authorityStr, requestPath)) {
                //设置请求头参数username
                ServerHttpRequest request = exchange.getRequest().mutate()
                        .headers(httpHeaders -> httpHeaders.add("username", oAuth2Authentication.getName())).build();
                exchange.mutate().request(request).build();
                return Mono.just(new AuthorizationDecision(true));
            }
        }
        return Mono.just(new AuthorizationDecision(false));
    }

}